#include "lib/decoders/wav/pvmdatasourcewav.h"

#include "lib/memory/pvmmemory.h"


PVMDataSourceWAV::PVMDataSourceWAV()
{
	criticalInitialized = false;
	refCount = 0;
	stream = NULL;
	initial = NULL;
	initialSize = 0;
	posInInitial = 0;
	dataOffset = 0;
	dataSize = 0;
}

PVMDataSourceWAV::~PVMDataSourceWAV()
{
	if(stream)
	{
		stream->Close();
	}

	if(criticalInitialized)
		PVMDeinitCritical(&critical);

	if(initial)
		delete[](initial);


	criticalInitialized = false;
	refCount = 0;
	stream = NULL;
	initial = NULL;
	initialSize = 0;
	posInInitial = 0;
	dataOffset = 0;
	dataSize = 0;
}

IPvmInStream& PVMDataSourceWAV::Stream()
{
	return *stream;
}


PVMDataSourceWAV* PVMDataSourceWAV::Create(IPvmInStream* srcStream, pvm_bool copyStream, pvm_int8* initial, pvm_int initialSize, pvm_int dataOffset, pvm_int dataSize, FmtHeader& header)
{

	PVMDataSourceWAV* src = new PVMDataSourceWAV();
	if(src->Init(srcStream, copyStream, initial, initialSize, dataOffset, dataSize, header)==false)
	{
		delete( src);
		return NULL;
	}

	return src;
}

PVMDataSourceWAV* PVMDataSourceWAV::CreateCopy()
{
	return Create(stream, true, NULL, 0, dataOffset, dataSize, formatHeader);
}

pvm_bool PVMDataSourceWAV::Init(IPvmInStream* srcStream, pvm_bool copyStream, pvm_int8* initial, pvm_int initialSize, pvm_int dataOffset, pvm_int dataSize, FmtHeader& header)
{
	criticalInitialized = PVMInitCritical(&critical);

	if(!criticalInitialized)
		return false;

	if(copyStream)
	{	
		IPvmInStream *newStream = srcStream->OpenCopy();
		if(!newStream)
		{
			return false;
		}
		stream = newStream;
	}
	else
	{
		stream = srcStream;
	}

	if(stream->Seekable() && initial!=0)
	{
		//we don't need initial data if stream is seekable
		delete[](initial);
		initial = NULL;
		initialSize = 0;
	}

	if(initial != 0)
	{
		if(stream->Tell() > initialSize)
		{
			//we can't add initial to output
			delete[](initial);
			initial = NULL;
			initialSize = 0;
		}
		else
		{
			this->initial = (pvm_int8*)initial;
			this->initialSize = initialSize;
		}
	}

	this->dataOffset = dataOffset;
	this->dataSize = dataSize;
	this->formatHeader = header;
	sampleSizeInBytes = formatHeader.bitsPerSample>>3;

	if(stream->Seekable())
		stream->Seek(dataOffset, EPVM_SEEK_BEGIN);//set pointer to data start

	offsetInSamples = 0;
	return true;
}

pvm_int PVMDataSourceWAV::GetRefCount()
{
	int refC;
	PVMEnterCritical(&critical);
	refC = refCount;
	PVMLeaveCritical(&critical);
	return refC;
}

pvm_int PVMDataSourceWAV::Release()
{
	bool needRet = true;
	int newRefC = refCount;
	PVMEnterCritical(&critical);
	if(refCount>0)
	{
		refCount--;
		if(refCount<0)
			refCount = 0;
		newRefC = refCount;
		needRet = false;
	}
	PVMLeaveCritical(&critical);
	if(needRet)
		return newRefC;

	if(refCount<=0)
	{
		refCount = 0;
		delete( this);
	}
	return newRefC;
}

pvm_int PVMDataSourceWAV::AddRef()
{
	int newRefC;
	PVMEnterCritical(&critical);
	refCount++;
	newRefC = refCount;
	PVMLeaveCritical(&critical);
	return newRefC;
}

pvm_int PVMDataSourceWAV::FillBuffer(void *buffer, pvm_int sizeInSamples)
{
	pvm_int samplesFilled = 0;	
	pvm_int offsetInBuffer = 0;
	pvm_int offsetInDstBuffer = 0;
	pvm_int bytesToFill = sizeInSamples*sampleSizeInBytes;
	pvm_int skipSize = dataOffset + dataSize;

	if(formatHeader.audioFormat==0x11)
	{
		if(sizeInSamples == 1)
			return 0;

		if(sizeInSamples & 1)
			sizeInSamples--;
		bytesToFill = sizeInSamples >> 1;
	}
	
	//simple memcpy or read
	if(initial!=NULL&&posInInitial<initialSize)
	{
		int samplesToCopy = sizeInSamples;
		int samplesInInitial = (initialSize - posInInitial)/sampleSizeInBytes;
		if(samplesToCopy>samplesInInitial)
			samplesToCopy = samplesInInitial;

		offsetInBuffer = samplesToCopy*sampleSizeInBytes;
		memcpy(buffer, ((char*)initial) + posInInitial, offsetInBuffer);
		posInInitial += offsetInBuffer;
		samplesFilled = samplesToCopy;
		offsetInDstBuffer = samplesFilled*sampleSizeInBytes;
		bytesToFill -= offsetInBuffer;
	}

	pvm_int bytesInStream = skipSize - (pvm_int)stream->Tell();

	if((samplesFilled<sizeInSamples) && (bytesInStream>0))
	{
		pvm_int byteRead = 0;

		if(bytesInStream<=0)
			return samplesFilled;

		if(bytesToFill>bytesInStream)
			bytesToFill = bytesInStream;
		
		byteRead = stream->Read(((char*)buffer) + offsetInDstBuffer, bytesToFill);
		switch(sampleSizeInBytes)
		{
		case 0:
			if(formatHeader.audioFormat==0x11)
				samplesFilled += byteRead << 1;
			break;
		case 1:
			samplesFilled += byteRead; break;
		case 2:
			samplesFilled += (byteRead>>1); break;
		case 4:
			samplesFilled += (byteRead>>2); break;
		default:
			samplesFilled += byteRead/sampleSizeInBytes;
		}
	}

	offsetInSamples += samplesFilled;
	return samplesFilled;
}

pvm_bool PVMDataSourceWAV::IsDataEnd()
{
	if(initial!=0&&initialSize>0&&posInInitial<initialSize)
		return false;
	if(stream->Tell() < dataOffset+dataSize)
		return false;
	return true;
}

pvm_bool PVMDataSourceWAV::SeekAtStart()
{
	if(stream->Seekable()==false)
		return false;

	offsetInSamples = 0;

	return stream->Seek(dataOffset, EPVM_SEEK_BEGIN);
}

pvm_bool PVMDataSourceWAV::SeekAtSamplePos(pvm_int64 pos)
{
	if(stream->Seekable()==false)
		return false;

	offsetInSamples = pos;

	return stream->Seek(dataOffset + pos*sampleSizeInBytes, EPVM_SEEK_BEGIN);
}

pvm_bool PVMDataSourceWAV::SetPositionMS(pvm_int32 position)
{
	pvm_int64 offsetInBlocks = position*formatHeader.sampleRate/1000;
	pvm_int64 offsetInByte = offsetInBlocks*formatHeader.blockAlign + dataOffset;
	if(stream->Seekable()==false)
		return false;

	offsetInSamples = (offsetInByte - dataOffset)/sampleSizeInBytes;

	return stream->Seek(offsetInByte, EPVM_SEEK_BEGIN);
}

pvm_int32 PVMDataSourceWAV::GetPositionMS()
{
	pvm_file_size filePos = (pvm_size)stream->Tell();
	if(filePos<0)
		return -1;
	pvm_int64 offsetInBlocks = (filePos - dataOffset) / formatHeader.blockAlign;


	return (pvm_int32)(offsetInBlocks*1000/formatHeader.sampleRate);
}
